package game;

import game.console.GameConsole;
import game.scrollable.INeverScrollable;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.io.StringWriter;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public abstract class AbstractGame extends AbstractRunnable implements Runnable, IRenderer
{
    private JFrame  frame;
    private JPanel  panel;
    
    private int     desiredFPS  = 20;
    
    private boolean GAME_RUNNING;
    
    protected AbstractGame(boolean enableScrolling)
    {
        // panel
        panel = new JPanel() {
            private static final long serialVersionUID = -1923410016305041430L;
            
            @Override
            protected void paintComponent(Graphics g)
            {
                render(g);
                // console
                GameConsole con = GameConsole.getInstance();
                if (con.isOpen())
                {
                    con.render(g);
                }
            }
        };
        panel.setLayout(null);
        panel.setIgnoreRepaint(true);
        // frame
        frame = new JFrame("Basic Game");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        Rectangle size = device.getDefaultConfiguration().getBounds();
        frame.setSize(new Dimension(size.width / 2, size.height / 2));
        // fullscreen
        if (this.getClass().isAnnotationPresent(FullScreen.class))
        {
         // make frame undecorated and not resizeable
            frame.setUndecorated(true);
            frame.setResizable(false);
            // go into full screen mode
            device.setFullScreenWindow(frame);
            frame.validate();
        }
        frame.getContentPane().setLayout(new BorderLayout());
        if (this instanceof INeverScrollable)
        {
            frame.getContentPane().add(panel, BorderLayout.CENTER);
        }
        else
        {
            JScrollPane scrollPanel = new JScrollPane(panel);
            // scrollPanel.add
            frame.getContentPane().add(scrollPanel, BorderLayout.CENTER);
        }
        Insets insets = frame.getContentPane().getInsets();
        setPreferredSize(frame.getSize().width - (insets.left + insets.right), frame.getSize().height - (insets.top + insets.bottom));
        GameConsole.getInstance().setSize(size.width, size.height / 3);
        // frame.pack();
        frame.setVisible(true);
        // frame.setResizable(false);
        frame.addComponentListener(new ComponentControl());
        // game listener
        final GameListener gameListener = GameListener.getInstance();
        panel.addKeyListener(gameListener);
        panel.addMouseListener(gameListener);
        panel.addMouseMotionListener(gameListener);
        panel.addMouseWheelListener(gameListener);
        // listener
        gameListener.addKeyListener(new KeyAdapter() {
            private boolean IGNORE_KEYBOARD = false;
            
            @Override
            public void keyPressed(KeyEvent e)
            {
                if (IGNORE_KEYBOARD)
                {
                    e.consume();
                }
                else
                {
                    IGNORE_KEYBOARD = true;
                    final GameConsole console = GameConsole.getInstance();
                    if (console.consoleKeyPressed(e))
                    {
                        if (console.isOpen())
                        {
                            console.closeConsole();
                        }
                        else
                        {
                            console.openConsole();
                        }
                        Logger.log("CONSOLE OPEN: " + console.isOpen());
                    }
                    if (console.isOpen())
                    {
                        console.fireKeyEvent(e);
                    }
                    else
                    {
                        final int stepsFPS = 5;
                        final int MAX_FPS = 1000;
                        final int MIN_FPS = 1;
                        switch (e.getKeyCode())
                        {
                            case KeyEvent.VK_MINUS:
                            {
                                int desiredFPS = AbstractGame.this.desiredFPS;
                                if (desiredFPS <= 10)
                                {
                                    desiredFPS--;
                                }
                                else
                                {
                                    desiredFPS -= stepsFPS;
                                }
                                if (desiredFPS < MIN_FPS)
                                {
                                    desiredFPS = MIN_FPS;
                                }
                                if (AbstractGame.this.desiredFPS != desiredFPS)
                                {
                                    Logger.log("-FPS: " + (AbstractGame.this.desiredFPS = desiredFPS));
                                    recalcDesiredDeltaLoop();
                                }
                                break;
                            }
                            case KeyEvent.VK_PLUS:
                            {
                                int desiredFPS = AbstractGame.this.desiredFPS;
                                if (desiredFPS < 10)
                                {
                                    desiredFPS++;
                                }
                                else
                                {
                                    desiredFPS += stepsFPS;
                                }
                                if (desiredFPS > MAX_FPS)
                                {
                                    desiredFPS = MAX_FPS;
                                }
                                if (AbstractGame.this.desiredFPS != desiredFPS)
                                {
                                    Logger.log("+FPS: " + (AbstractGame.this.desiredFPS = desiredFPS));
                                    recalcDesiredDeltaLoop();
                                }
                                break;
                            }
                        }
                    }
                    IGNORE_KEYBOARD = false;
                }
            }
        });
        // focus
        panel.requestFocus();
    }
    
    protected void setPreferredSize(int width, int height)
    {
        panel.setPreferredSize(new Dimension(width, height));
    }
    
    protected Dimension getFrameSize()
    {
        return frame.getSize();
    }
    
    private class ComponentControl extends ComponentAdapter
    {
        private boolean   isResizing;
        private long      resizeTime = 0;
        private final int DELAY      = 600;
        
        private class ResizeThread implements Runnable
        {
            @Override
            public void run()
            {
                while (true)
                {
                    try
                    {
                        while (isResizing)
                        {
//                            Logger.log("RESIZING");
                            long delay = System.currentTimeMillis() - resizeTime;
                            if (delay > DELAY)
                            {
                                Logger.log("NEWSIZE: " + newSize);
                                frame.setPreferredSize(newSize);
                                frame.getContentPane().setPreferredSize(newSize);
                                GameConsole.getInstance().setSize(newSize.width, newSize.height / 3);
                                if (AbstractGame.this instanceof INeverScrollable)
                                {
                                    ((INeverScrollable) AbstractGame.this).recalcSize(newSize);
                                }
                                frame.pack();
                                Logger.log("FRAME-SIZE: " + frame.getSize());
                                Logger.log("PANEL-SIZE: " + frame.getContentPane().getSize());
                                isResizing = false;
                            }
                            else
                            {
                                Thread.sleep(delay);
                            }
                        }
                        Thread.sleep(DELAY);
                    }
                    catch (InterruptedException e)
                    {
                        // e.printStackTrace(System.err);
                    }
                }
            }
        }
        
        private Dimension newSize;
        private Thread    resizeThread;
        
        public ComponentControl()
        {
            resizeThread = new Thread(new ResizeThread(), "Resizer");
            resizeThread.start();
        }
        
        @Override
        public void componentResized(ComponentEvent e)
        {
            newSize = e.getComponent().getSize();
            resizeTime = System.currentTimeMillis();
            isResizing = true;
            resizeThread.interrupt();
        }
    }
    
    private int recalcDesiredDeltaLoop()
    {
        return (1000 * 1000 * 1000) / desiredFPS;
    }
    
    @Override
    public synchronized void update()
    {
        super.update();
        // title
        StringBuilder title = new StringBuilder(getClass().getSimpleName()).append(" - ");
        title.append(String.valueOf(desiredFPS)).append(" FPS; ");
        title.append(getStatus()).append("; ");
        title.append(getInfo());
        frame.setTitle(title.toString());
    }
    
    @Override
    public void run()
    {
        reset();
        GAME_RUNNING = true;
        start();
        while (GAME_RUNNING)
        {
            try
            {
                long beginLoopTime = System.nanoTime();
                SwingUtilities.invokeAndWait(new Runnable() {
                    
                    @Override
                    public void run()
                    {
                        panel.repaint();
                    }
                });
                update();
                long endLoopTime = System.nanoTime();
                long deltaLoop = endLoopTime - beginLoopTime;
                long desiredDeltaLoop = recalcDesiredDeltaLoop();
                if (deltaLoop <= desiredDeltaLoop)
                {
                    try
                    {
                        Thread.sleep((desiredDeltaLoop - deltaLoop) / (1000 * 1000));
                    }
                    catch (InterruptedException e)
                    {
                        // Do nothing
                    }
                }
            }
            catch (Exception ex)
            {
                StringWriter sw = new StringWriter();
                ex.printStackTrace(new PrintWriter(sw));
                Logger.log(sw.toString());
                GAME_RUNNING = false;
            }
        }
    }
}
